Projet collectif de fin de semestre pour le cours d’Aurélien Berra, “Humanités numériques 3”, à l’Université Paris Nanterre, décembre 2020.
Si Aurélien Berra ne fait pas formellement partie de la liste des auteurs du présent document, le présent travail n’aurait pas été possible sans lui : nous souhaitons lui adresser tous nos remerciements, non seulement pour ses cours qui nous ont initiés aux outils ici manipulés, pour son énoncé stimulant posant le problème auquel nous nous confrontons ici, mais encore pour sa disponibilité ainsi que ses nombreuses remarques, ressources et indications, prodiguées au cours de notre travail puis à la suite d’une lecture très attentive d’une première version de ce document. Nous avons tenté d’en tenir compte et de les intégrer autant que possible, si bien que ce travail est aussi un peu le sien.
L’entrée des humanités classiques dans le monde des humanités numériques a eu de nombreux avantages. Un de ceux-ci a révolutionné la vie des chercheurs dans ce domaine : la création de sites comme le Thesaurus Linguae Graecae ou Perseus. Ces deux sites offrent un grand nombre de fonctionnalités inédites : le canon des auteurs et des œuvres est immense et accessible gratuitement sur Perseus et avec un abonnement pour le TLG. Étudier une œuvre est grandement facilité par la disponibilité de plusieurs dictionnaires, des analyses grammaticales potentielles, des mises en parallèle entre les œuvres que l’on souhaite, et même des analyses statistiques.
Ces deux sites se fondent avant tout sur des bases de données. Celles-ci concentrent de nombreuses informations : les auteurs, leurs travaux, leurs dates d’activité, leur localisation géographique… Une de ces données, créée pour mieux organiser les autres, est le code de chaque auteur. Celui-ci est composé de quatre chiffres, parfois suivis par un point et plusieurs autres chiffres : Homère, par exemple, a le code 0012. Mais un problème se pose : ce code paraît avoir été attribué de manière tout à fait arbitraire. Il ne suit pas d’ordre particulier : ni l’ordre alphabétique, ni l’ordre chronologique. De même, les nombres ne sont pas donnés dans un ordre continu car certains manquent : c’est le cas du code 2500, pour ne citer qu’un exemple.
Cependant, étant donné l’importance qu’ont prise ces bases de données dans l’étude des humanités classiques, il semble de plus en plus nécessaire de comprendre leur fonctionnement, notamment lorsque celui-ci paraît aberrant comme en ce qui concerne ces nombres. Il faut pouvoir se référer à ces codes pour une meilleure utilisation de ces bases de données, en particulier dans le cadre d’une collaboration. Tout d’abord, il faut noter que l’importance de ces identifiants est tout d’abord qu’ils sont numériques, et, surtout, qu’ils sont uniques ! Chaque auteur a un identifiant qui lui est propre, et sa qualité de nombre permet une étude grâce aux humanités numériques. C’est pourquoi, pour se référer à ces codes et les utiliser correctement, il s’agit de comprendre la façon dont ils ont été attribués, en particulier à l’aide d’outils comme le langage ‘R’.
Allier humanités classiques et humanités numériques pour expliquer ces outils numériques devenus fondamentaux dans les humanités classiques semble alors être un juste retour des choses.
Pour notre étude, nous nous concentrerons sur les identifiants des auteurs, laissant de côté la logique de numérotation des œuvres : à première vue, chaque auteur a une numérotation propre à ses œuvres (001, 002…) mais qui pourrait suivre des logiques communes, par exemple une classification chronologique par date d’écriture. C’est tout ce que nous en dirons, car il nous paraît plus intéressant de commencer par nous focaliser sur les auteurs ; après tout, c’est le premier identifiant apparaissant sur une œuvre (0012.001, 0012.002…). Analyser plus finement la logique de numérotation des œuvres demanderait de constituer une base de données à analyser de manière différente : cela pourrait faire l’objet d’un travail ultérieur.
Nous cherchons ici à expliquer une variable, à savoir un identifiant, par d’autres variables potentielles, comme le nom, la date, le lieu ou le genre littéraire.
Rappelons qu’une “variable” est une caractéristique d’un élément considéré. Dans un tableau, les “variables” correspondent généralement aux colonnes, et les éléments considérés aux lignes. On peut aussi parler, pour les lignes, d’“entrées” ou d’“individus”. Pour chaque individu, chaque variable prend une modalité particulière, c’est le “contenu” de la variable. Les modalités sont ainsi l’ensemble des valeurs que peuvent prendre une variable. Par exemple, l’identifiant est une variable qui a une forme particulière, à savoir un numéro de 1 à 4 chiffres, et pour l’individu “Platon”, la variable “identifiant” prend la modalité “0059”.
Considérons un instant les types des variables qui sont en jeu, avant de présenter les étapes qui vont nous être nécessaires dans notre étude.
Pour y voir plus clair, distinguons la “nature” des variables qui sont en jeu. On pourrait différencier :
Les variables “continues” : il s’agit de variables numériques, sur lesquelles on peut effectuer des opérations mathématiques (calcul de moyenne, minimum, maximum…). Il s’agit par exemple des identifiants sous la forme de numéros ou de dates. (Notons au passage que les catégories que nous proposons ne correspondent pas scrupuleusement à des catégories statistiques consacrées : des puristes pourraient ici arguer notamment que lorsqu’il s’agit de nombres entiers, comme les identifiants ou les dates sous la forme d’années, on est face à des variables qu’il faudrait dire discrètes plutôt que continues. Qu’importe, nous n’en voyons pas ici l’importance.)
Les variables “catégorielles” : il s’agit de “catégories” dans lesquelles on range les individus, des sortes d’étiquettes, en nombre limité, qu’on applique chacune à plusieurs individus. Dans notre cas, elles seront sous la forme de chaînes de caractères. Il s’agit par exemple des genres littéraires ou des lieux. Les initiales des noms peuvent également en relever.
Les variables “singulières” : il s’agit de variables dont le contenu est singulier, et généralement unique, à chaque individu considéré. Typiquement, c’est le cas des noms des auteurs. La distinction avec les variables continues repose sur le fait qu’il s’agit de chaînes de caractères qu’il est plus difficile de transcrire en chiffres, et donc dont la manipulation peut différer.
Les variables continues présentent l’avantage de correspondre à une échelle déterminée : on peut facilement dire que 3 est supérieur à 1, ou que -500 est antérieur à -400. Néanmoins, les variables que nous avons appelées catégorielles ou singulières, peuvent également être “ordonnées”, selon un critère déterminé : pour les noms en particulier, l’ordre alphabétique paraît être un mode d’arrangement qui fait sens. Pour les lieux, on pourrait les ranger par catégorie emboîtée (ville, région, pays), ou encore selon un ordre particulier (par exemple, d’ouest en est). Ces variables “ordonnées” se rapprochent alors un peu des variables continues, car, comme ces dernières, on peut désormais dire si telle modalité vient après ou avant telle autre modalité dans l’ordre qu’on a choisi : “Aristote” vient avant “Platon” pour la variable “nom” ordonnée par ordre alphabétique.
Notons que nous avons cité ici les variables les plus évidentes – et les plus accessibles – qui semblent à première vue avoir le plus de sens. Néanmoins, il se pourrait que nous laissions alors de côté d’autres considérations, comme par exemple des dates particulières, de découverte des manuscrits par exemple ; ou encore, nous pourrions être trop peu précis en ce qui concerne le nom des auteurs : peut-être ceux-ci ont-il évolué dans le temps et dans l’espace, selon les langues et les appellations, ou bien peut-être faudrait-il établir des distinctions selon ce qui pourrait correspondre à des éléments comme le nom et le prénom.
Pour que les relations entre ces variables aient du sens et pour saisir si elles sont corrélées ou non, et si oui en quoi, nous allons mobiliser des analyses graphiques : plutôt que des analyses mathématiques à l’aveuglette, les graphiques paraissent offrir l’approche la plus flexible et compréhensible. Nous pourrons alors projeter les variables sur les axes, d’un plan habituel en deux dimensions, et éventuellement user des couleurs pour ajouter en troisième lieu une nouvelle dimension.
Il faut noter que nous devrons a priori toujours projeter la variable à expliquer, c’est-à-dire celle des identifiants (à moins que nous ne cherchions à distinguer des corrélations intermédiaires qui sont la cause première des relations observées – par exemple si le lieu est corrélé avec la date, on pourrait, en ne regardant que les identifiants et les lieux, penser qu’il y a corrélation, quand la véritable corrélation serait en réalité entre les identifiants et les dates).
A priori, les variables continues se prêtent bien à la projection sur un axe orienté, car elles correspondent à une échelle. Cela est vrai aussi, quoique dans une moindre mesure, pour les variables ordonnées, dont l’ordre peut transparaître au fil de l’axe. Les variables catégorielles (ordonnées ou non) quant à elles se prêtent bien à l’utilisation de couleurs, du fait qu’elles devraient être relativement peu nombreuses, et donc distinguables grâce à des couleurs différentes. Lorsqu’elles sont ordonnées (voire même, dans un autre style, continues), on pourrait de plus faire appel à un dégradé – par exemple, en attribuant des couleurs plus claires aux valeurs “inférieures”, et des couleurs plus sombres aux valeurs “supérieures”.
L’ajout d’une troisième dimension par la couleur pourra nous aider à distinguer une évolution dans le cas où la classification changerait de logique en cours de route, ou obéirait en fait à plusieurs logiques : par exemple, si les auteurs étaient regroupés par lieu, puis, au sein de chaque groupe constitué pour un lieu donné, s’ils étaient classés par ordre alphabétique. On verrait ainsi, sur nos graphiques, soit des séquences d’éléments qui se suivent dans l’ordre alphabétique, chacune d’une couleur particulière (si on projette le nom sur un axe, et qu’on colore les lieux), soit des points regroupés ensemble par lieu, avec sur chacun un dégradé de couleurs correspondant à l’ordre alphabétique (si on projette les lieux sur un axe, et qu’on colore les initiales).
Nous devrons constituer nos données, en récupérant les informations appropriées (correspondance entre identifiants et noms des auteurs, dates des auteurs, genre littéraire…), puis faire en sorte de les traiter. Successivement, nous devrons :
Récupérer des données (voir Chargement des données), depuis internet notamment, à partir de bases de données déjà constituées ;
Formater ces données pour les rendre exploitables (voir 1. Formatage des données), notamment en s’assurant de l’homogénéité des catégories et en transformant certaines variables textuelles en variables numériques ;
Réaliser des analyses à partir des données formatées (voir 2. Analyses graphiques), c’est-à-dire, ici, essentiellement des graphiques.
Chargement des bibliothèques externes.
pacman::p_load(
here, jsonlite, knitr, skimr, tidyverse
)
Configuration du rendu.
# In order to globally disable messages such as minor warnings from external functions in the final rendering.
opts_chunk$set(
message = FALSE,
warning = FALSE
)
Déclaration de variables globales (qu’on pourrait aussi appeler des constantes).
# Input directory.
input_dir <- here("projet_final/data/")
# On Windows, the package we use here deletes the final "/" although we need it, therefore we must add it again.
if (.Platform$OS.type == "windows") input_dir <- paste0(input_dir, "/")
# Variants for NA in the retrieved data.
na_variants <- c(
"", "?", "\n", "...",
"NA", "n.a.", "n",
"none", "None", "one",
"unknown", "Unknown",
"Various", "Varia",
"Uncertain"
)
# Note that we will declare colours in relevant section to present them,
# which is in the middle of the second section.
Nous déclarons ci-dessus une fois pour toutes les différentes chaînes de caractères pouvant être utilisées pour indiquer des données manquantes, dans les différentes variables des données originales, repérées au fil de nos explorations. Elles seront remplacées en une fois au début du formatage des données.
La plupart des données que nous mobilisons sont récupérées depuis un tableur en ligne du Projet Perseus. Le tableur contient des entrées d’œuvres antiques, ainsi que des informations sur leurs auteurs qui pourraient nous être utiles, en particulier : nom, lieu, dates d’activité, de début et de fin. Il contient également les identifiants qu’on cherche à expliquer, à savoir les identifiants TLG et PHI.
phi_works_raw_df <- read_csv(paste0(input_dir, "latin_authors_by-perseus.csv"))
tlg_works_raw_df <- read_csv(paste0(input_dir, "greek_authors_by-perseus.csv"))
En complément, nous utiliserons la liste des genres récupérée depuis les fichiers du CLTK (Classical Language ToolKit, la boîte à outils des langues classiques). Il ne s’agit ici que des auteurs grecs – il aurait été possible de récupérer l’épithète de genre sur les noms des auteurs TLG tels qu’ils sont formatés dans la liste du Projet Perseus, mais autant profiter de ce formatage déjà établi.
tlg_epithets_raw_df <-
read_json(paste0(input_dir, "greek_authors-epithets_by-cltk.json")) %>%
enframe("genre", "code")
Notons que nous conserverons, dans l’environnement de développement, les tableaux avec les données d’origine chargés ici : cela nous permettra en effet, au cours du développement de nos analyses, d’éventuellement aller vérifier quelles étaient les données de base correspondantes.
Pour consulter les données finales obtenues, rendez-vous à la fin de la section !
Pour rendre les données récupérées exploitables, nous devons en formaliser la structure en les “encodant” de sorte qu’elles correspondent aux analyses que nous souhaitons mener.
En fin de compte, les variables que nous obtiendrons seront les suivantes :
| Nom | Description | Exemple | Nature | Type | Source d’origine |
|---|---|---|---|---|---|
code |
identifiant | 0059 |
nombre | continue | Perseus |
init |
initiale | P |
caractère | catégorielle ordonnée |
Première lettre de “nom” |
name |
nom | Plato Phil. |
caractères | singulière ordonnée |
Perseus |
genre |
genre littéraire (TLG uniquement) |
Philosophici/-ae |
caractères | catégorielle | CLTK |
place |
lieu | Greece |
caractères | catégorielle | Perseus |
date |
date | -500 |
nombre | continue* | Copie de “date d’activité” |
date_active |
date d’activité, ou siècle |
-500 |
nombre | continue* | Perseus |
date_begin |
date “de départ”, ou de naissance |
-427 |
nombre | continue | Perseus |
date_end |
date “de fin”, ou de mort |
-348 |
nombre | continue | Perseus |
Pour obtenir ces données finales, exploitables statistiquement, nous avons dû faire plusieurs choix.
Ces équivalents-caractères ressemblent à ceci :
“200bce?” correspond, avec une incertitude (?) à 200 avant notre ère (bce pour Before Common Era), et est traduit sous forme numérique en -200
“100ce-200ce” désigne un intervalle compris entre 100 et 200 de notre ère (ce pour Common Era). Nous prenons alors la moyenne des deux extrémités, ce que nous donne 150.
En ce qui concerne le “siècle”, le fait de prendre la moyenne lorsqu’on est face à un intervalle brise l’homogénéité de la mesure. “100ce-200ce” correspond à “entre le premier et le second siècle”, néanmoins “150” ne correspond plus à un siècle, mais plutôt à un demi-siècle. De plus, nous avons un problème au tournant de l’ère : “100bce” correspond au premier siècle avant (années -100 à -1) et “100ce” au premier siècle après (années 1 à 100) : l’échelle des siècles passe directement du -1 au 1, sans 0 intermédiaire, alors que la considérer comme un nombre “normal” la fait passer par un 0.
Ce dernier souci étant de peu d’importance et non évident à corriger, nous signalons simplement que les dates entre -100 et 100, quand nous prendrons les siècles (date_active ou date), seront étalées deux fois plus que relativement à des données correctement homogènes.
Pour les lieux, nous n’avons conservé que le pays, pour gagner en généralité et avoir des catégories plus amples – les données de départ comportaient également parfois la région et/ou ville. Lorsque plusieurs lieux étaient indiqués, nous n’avons arbitrairement conservé que le dernier de la liste. Il aurait été possible, mais alors plus fastidieux, de récupérer ces lieux multiples pour les formater convenablement, en les attribuant à chaque auteur sous la forme d’un objet “liste”, pour ensuite dupliquer chaque ligne contenant plusieurs lieux. Cela nous aurait permis de faire une analyse plus complète des lieux ; un même auteur avec un seul identifiant aurait alors pu être représenté par plusieurs points, de sorte qu’il aurait été possible de repérer, sur les nuages de points, le lieu “aberrant” et le lieu “correct” vis-à-vis de la logique dominante qui se serait dégagée. D’autre part, on trouve diverses appellations pour un espace géographique identique, en totalité ou en partie. Par exemple, nous observons les occurrences “Ionia”, “Turkey”, “Anatolia” ou encore “Tunisia”, “North Africa”, “Africa”. Ces désignations sont probablement liées en partie à l’époque à laquelle appartient l’auteur : par exemple, “Ionia” serait l’appellation plutôt attribuée aux poètes archaïques et philosophes présocratiques alors que “Turkey” le serait plutôt celle des les auteurs chrétiens. Cependant, cette explication ne semble pas s’appliquer à tous les lieux et à tous les auteurs puisque, comme nous pouvons l’observer dans les graphiques des paragraphes consacrés aux identifiants et lieux, le lieu “Turkey” concerne des auteurs dès le 4ème siècle avant notre ère. Comprendre la logique qui a conduit à désigner ces territoires en fonction des dates, peut-être des genres, pourrait faire l’objet d’une étude à part entière. Nous avons cependant gardé les désignations de lieux telles quelles.
Pour les genres, lorsqu’un auteur appartenait à plusieurs genres, nous n’avons conservé que le premier genre qui apparaissait par ordre alphabétique. Nous avons adopté la même méthode que pour les lieux multiples quant à la possibilité d’user d’objets “liste”.
On remarquera dans le tableau d’auteurs grecs au chapitre “Données finales obtenues” que certains genres littéraires figurent dans la colonne des noms d’auteurs (issue de Perseus) alors qu’elles sont manquantes dans celle des genres (dans le tableau importé CLTK). Une observation plus attentive permet cependant de remarquer que ces “genres” qui apparaissent dans la colonne (exemple: epistulae) font partie intégrante du “name”; ils ne sont pas qualifiants d’un auteur mais désignent un corpus qualifié par le nom d’un auteur - réel, inconnu ou réinventé- désigné par la tradition. Par exemple, Alexandri Magni epistulae ne désigne pas Alexande le Grand, auteur d’epistulae mais des epistulae dont l’auteur est Alexandri Magni. Nous avons gardé les noms tels qu’ils apparaissaient dans les données que nous utilisions.
Nous allons formater les données que nous avons récupérées pour qu’elles correspondent aux variables que nous avons décrites, en procédant successivement variable par variable.
Pour chaque section de formatage, nous allons procéder dans l’ordre suivant :
Cette démarche nous permettra d’agir, au cours du développement, avec flexibilité : nous pourrons notamment réaliser des tests au fur et à mesure que nous ajustons et perfectionnons nos fonctions de formatage, dans un aller-retour entre section de tests et section de création de fonctions.
Il y aura ainsi cinq parties :
Pour finir, la section conclusive contiendra l’ensemble des données ainsi obtenues.
Puisque les données récupérées depuis le tableur du projet Perseus consistent en une liste d’œuvres, nous devons en faire une liste d’auteurs, en récupérant les données qui nous intéressent.
On conserve, pour chaque auteur, les variables suivantes :
| Description | Nom dans notre tableau | Nom dans le tableau original |
|---|---|---|
| nom | name |
TLG / PHI AUTHOR NAME |
| identifiant | code |
TLG / PHI # |
| lieu | place |
Location |
| date d’activité | date_active |
Century/Dates Active |
| date “de départ” ou de naissance | date_begin |
Begin Date/Birth Year |
| date “de fin” ou de mort | date_end |
End Date/Death Year |
Pour la date d’activité, il s’agit le plus souvent de siècles, formatés comme des années multiples de 100 (eg. “100 CE” correspond au premier siècle). Pour les dates de début ou de fin, il s’agit d’années ou de siècles (formatés alors comme précédemment).
# We will compute the authors dataframes from the works dataframes.
# Didactic demo code: selecting and renaming columns.
tlg_authors_df <-
tlg_works_raw_df %>%
transmute(
code = `TLG#`,
name = `TLG AUTHOR NAME`,
place = `Location`,
date_active = `Century/Dates Active`,
date_begin = `Begin Date/Birth Year`,
date_end = `End Date/Death Year`
)
# Since we will be doing exactly the same operations on both works list,
# we will use functions (to avoid hurtful copy-pasting, which is inconvenient for code modifications).
# Hopefully, column names are almost the same in both retrieved documents.
# Passing column names through a function is not obvious,
# but it's possible if we use what is called quosures.
# @see https://github.com/r-lib/rlang/issues/116
# @see https://stackoverflow.com/questions/48062213/dplyr-using-column-names-as-function-arguments
#' Extract authors from raw works dataframe.
#' @param works_raw_df Dataframe. Works as dataframe, extracted from data.
#' @param specific_filling String. String that is specific to the columns names in the data.
#' e.g. "PHI" is used in the latin works dataframe for naming some columns,
#' whereas "TLG" is prefered in the greek one.
#' @return Dataframe. Authors as dataframe.
extract_authors <- function(works_raw_df, specific_filling) {
# Compute some column names that are specific to the given data frame.
code_col_name <- paste0(specific_filling,"#")
author_col_name <- paste0(specific_filling," AUTHOR NAME")
# Build the data frame.
authors_df <-
# 1. Keep only the columns we need from raw data, rename them.
works_raw_df %>%
transmute(
code = !!sym(code_col_name), # Identifiant de l'auteur
name = !!sym(author_col_name), # Nom de l'auteur
place = `Location`,
date_active = `Century/Dates Active`,
date_begin = `Begin Date/Birth Year`,
date_end = `End Date/Death Year`
) %>%
# 2. Drop trailing carriage returns that mess things up.
mutate_all(~ str_replace_all(., "\n$", "")) %>%
# 3. Trim all.
mutate_all(~ str_trim(.)) %>%
# 4. Keep only the first four digits from the code,
# and parse it to a numerical value for further analysis.
# Only the first four are relevant to the author,
# other trailing characters in the code
# can characterize the work, for instance
# (remember it's a list of works we have at the beginning).
# Beware: When numbers are <1000, they are not always padded on the right.
mutate(
code = code %>%
str_extract("^\\d{1,4}") %>%
parse_number()
) %>%
# 5. Select only one row per unique code,
# that is to say : keep only one row per author,
# instead of one row per work.
# We could have used the author's name,
# but using directly the code fails to include authors that do not have any code,
# and since we want to analyze codes,
# we do not have to include authors that do not have any code.
# rq: It keeps one row with NA as code, we will remove it hereafter.
distinct(code, .keep_all = TRUE) %>%
# 6. Sort by code
arrange(code) %>%
# 7. Replace NA-equivalent values to proper NA.
mutate_all(~ replace(., . %in% na_variants, NA)) %>%
# 8. Drop rows that don't have any code.
drop_na(code)
# Return the data frame.
return(authors_df)
}
phi_authors_df <- extract_authors(phi_works_raw_df, "PHI")
tlg_authors_df <- extract_authors(tlg_works_raw_df, "TLG")
Nous n’avons pas vérifié de manière systématique les erreurs qui peuvent être présentes dans les données que nous avons récupérées, nous corrigeons seulement celles qui relèvent de valeurs aberrantes immédiatement visibles lors de nos formatages ultérieurs, manifestement dues à des erreurs de frappe, comme par exemple l’indication de “Rhodes” comme siècle d’activité.
# Some manual corrections of errors in the data.
# We need to:
# - place <-> date_active, for 2353 (instead of "500 BCE" as place and "Rhodes" as date)
# - place -> date_active, for 1784 (instead of "400 BCE" as place)
# - date_active -> NA, for 2514 (instead of "Rome, Italy")
# - date_end -> "495 BCE/478 BCE" for 0237 (instead of "CE" when the other two are "BCE")
tmp_place_2353 <-
tlg_authors_df %>%
filter(code == 2353) %>%
pull(date_active)
tlg_authors_df <- tlg_authors_df %>%
mutate(
date_active = case_when(
code == 1784 | code == 2353 ~ place,
code == 2514 ~ NA_character_,
TRUE ~ date_active
),
place = case_when(
code == 1784 ~ NA_character_,
code == 2353 ~ tmp_place_2353,
TRUE ~ place
),
date_end = case_when(
code == 237 ~ "495 BCE/478 BCE",
TRUE ~ date_end
)
)
# Remove variables that are useless in the future.
remove(tmp_place_2353)
Les dates extraites de nos données d’origine sont peu mobilisables dans des analyses statistiques, elles manquent de cohérence et sont difficilement interprétables programmatiquement, ne correspondant à aucun format structuré.
Nous allons donc les formater convenablement.
Les données d’origine sont textuelles et contiennent quelques erreurs et irrégularités. Par conséquent, le travail demandé est important.
Nous allons commencer par établir les expressions régulières : d’abord celles qui semblent correspondre plus ou moins aux données de départ, puis celles auxquelles nous souhaitons faire correspondre les données “mises au propre”.
Une fois cet objectif déterminé, nous devrons tout d’abord re-formater les dates en restant dans le domaine des chaînes de caractères, de telle sorte qu’elles correspondent scrupuleusement à l’expression régulière finale que nous souhaitons, puis, une fois ce travail accompli, nous pourrons les convertir en nombre facilement.
L’expression régulière finale aura la forme suivante :
"(ante|post)?(\\d{1,4})(ce|bce)(\\?)?"
ante 200 bce ?
avant / après la date considérée
année
avant / de notre ère
indicateur de date incertaine
La date peut également être un intervalle : ce sont alors deux dates formatées comme ci-dessus liées par un tiret.
Nous ne retiendrons néanmoins sous la forme numérique, pour chaque date, que les deux parties centrales, à savoir l’année et si cela est avant notre ère ou de notre ère. “ante200bce?” donnera donc “-200”.
Comme nous l’avons déjà évoqué, lorsque nous seront face à un intervalle, nous prendrons la moyenne des extrémités : “100ce-200ce” donne “150”.
# We will need a date parser.
# We could extract the first (most-left) match from the sequence of digits,
# which would be good enough,
# but this does not deal with BCE/CE, nor does it assure data correctness.
# So we will parse things in a neatlier way.
# Here we have to deal with the date before common era (BCE) and within common era (CE).
# This causes a few problems since most specifications and packages do not deal with this initially.
# - Timestamps usually start at 1970-01-01.
# Dates before stored as negative values, eg.
lubridate::ymd_hms("1969-12-31T23:59:59") # == -1
# - ISO 8601 year starts at 0000-01-01 which is the 1st January of 1 BCE
# (there is no such thing as year zero).
# @see https://en.wikipedia.org/wiki/ISO_8601
# @see https://en.wikipedia.org/wiki/Year_zero
# Although, there are some "0 CE" and "0 BCE" in our data that are unclear.
# A few remarks about the lubridate package.
# The lubridate package
# - Does not handle BCE date initially
# - Will not be able to match our data as raw values (that have intervals and CE/BCE specifications)
# @see https://lubridate.tidyverse.org/
# @see https://raw.githubusercontent.com/rstudio/cheatsheets/master/lubridate.pdf
# @see https://github.com/tidyverse/lubridate/issues/2
# If we want to deal with BCE date anyway, we could do something such as:
lubridate::ymd("0000-01-01") - lubridate::years(1) # == "-001-01-01"
# @WARN: Since "0000-01-01" is 1 BCE, "-0001-01-01" would be 2 BCE.
# We could write a helper function to retrieve correct dates from this,
# also taking into account months, days, etc., that we do not need here.
On prépare les données qui nous serviront pour nos tests.
# Test sample from the data,
# to build regex fitted for most common cases.
test_dates <- c(
"400 BCE",
"200 BCE?",
"10 CE",
"01 CE",
"200 CE",
"200 CE?",
"1050 CE",
"ante 200 CE",
"Ante 100 CE",
"post 200 CE",
"90 CE/95 CE",
"100 BCE-0 CE",
"600 BCE-500 BCE",
"100 CE-200 CE?",
"100 BCE-100 CE",
"284 BCE/275 BCE",
"1100 CE-1200 CE",
"ante 100 CE-200 CE",
# Writing errors?
"500 BCE-",
"200 - 400 CE"
)
# Every date in the data, to test them with our functions.
test_cols_dates <- c(
tlg_authors_df$date_active,
tlg_authors_df$date_begin,
tlg_authors_df$date_end,
phi_authors_df$date_active,
phi_authors_df$date_begin,
phi_authors_df$date_end
)
# Regular expressions.
# rq: The (?i) flag for case-insensitivity works with the stringr package.
# It may end up duplicated here, but it does not seem to matter.
# 1. Loose regular expressions,
# avoiding any re-writing of data.
date_loose_single_regex <- "(?i)((ante|post|pre|before)? ?\\d{1,4} ?(CE|BCE)\\??-?)"
date_loose_regex <- paste0(date_loose_single_regex," ?((-|/) ?",date_loose_single_regex,")?")
# 2. Strict regulation of expressions,
# that requires pre-formatting of data,
# such as white-spaces removing and terms homogeneity.
date_single_regex <- "(ante|post)?(\\d{1,4})(ce|bce)(\\?)?"
# Capturing groups (starting at 2, in columns, resulting match() matrix in R):
# - 2-5 are for left-side (start of date interval)
# - 6-9 are for right-side (end of date interval)
# - 2, 6: "ante" or "post" (optional)
# - 3, 7: year as number
# - 4, 8: "ce" or "bce"
# - 5, 9: whether it is an approximation (optional)
date_regex <- paste0(date_single_regex,"(?:-",date_single_regex,")?")
# Test to adjust regex gradually, we look at what does not pass the regex.
test_dates %>% str_subset(paste0("^",date_loose_regex,"$"), negate = TRUE)
test_cols_dates %>%
setdiff(na_variants) %>%
str_subset(paste0("^",date_loose_regex,"$"), negate = TRUE)
Après plusieurs essais, on comprend qu’une expression régulière ne va pas suffire pour récupérer les informations depuis les données d’origine : un formatage supplémentaire préalable se révèle nécessaire.
#' Format dates from a string vector so they match our strict regex.
#' @param str_v String vector containing date to format.
#' @return String vector with dates formatted.
format_dates <- function(str_v) {
res_str_v <-
str_v %>%
# 1. Lowercase.
str_to_lower() %>%
# 2. Remove every white-spaces, because of their meaninglessness.
str_replace_all(" ", "") %>%
# 3. Replace irregular values to homogenize.
str_replace_all(c(
"/" = "-",
"pre|before" = "ante"
)) %>%
# 4. Fix some writing errors.
# rq: Here we temporary use white-spaces
# as they are nicer than look-arounds, eg. "(?<=\d)bcw(?=-|$)"
str_replace_all("([a-z]+)", " \\1 ") %>%
str_replace_all(c(
" e " = "ce",
" cer " = "ce",
" be " = "bce",
" bc " = "bce",
" bcw " = "bce",
" bcebce " = "bce"
)) %>%
str_replace_all(" ", "") %>%
# 5. Remove trailing "-".
str_replace_all("-$", "") %>%
# 6. Add ce/bce on the left side when it is missing.
# @WARN: Here, we use lookahead to copy the right side to the left side,
# as if it were a common factor.
# In our data things seem to be doing fine,
# but this method does not handle the hypothetical case where left-side would be BCE and right CE.
# In this case, we would know that, considering left comes chronologically before right:
# - If right is BCE, then left is also BCE;
# eg. 200-100bce must be 200bce-100bce
# - If right is CE, then left is BCE if digits on the left are greater than the ones on the right;
# eg. 200-100ce must be 200bce-100ce
# - If there are not, we cannot be sure if left is CE or BCE.
# eg. 50-100ce cannot be determined
# So we would need to match, parse to number, and compare each side.
str_replace_all("(\\d)(?=-\\d+(bce|ce))", "\\1\\2")
# 7. Delete elements that still don't pass the regex,
# there are not valid dates and we cannot make them fit into one.
# In our data, we are left with "rhodes" and "rome,italy".
# They are not dates. We fix those two in previous sections of the document.
res_str_v <- res_str_v %>%
str_detect(paste0("^",date_regex,"$")) %>%
if_else(res_str_v, NA_character_)
return(res_str_v)
}
# Test to improve formatting gradually, we look at what does not pass the regex.
test_cols_dates %>%
format_dates() %>%
str_subset(paste0("^",date_regex,"$"), negate = TRUE)
#' Helper function.
#' Convert one half of the interval into a number.
#' @param match String vector. Relevant portion of a match from convert_dates()
#' @return Number, date as number
hlp_convert_single_date <- function(match) {
is_bce <- if_else(match[, 3] == "bce", TRUE, FALSE)
number <- parse_number(match[, 2])
number <- if_else(is_bce, -number, number)
return(number)
}
#' Convert a date into a number, which is the year.
#' CE years are positive values, BCE years are negative values.
#' If the date includes an interval, take the mean of the interval.
#' @param str_v String vector containing date to convert.
#' @return Number vector with converted dates.
convert_dates <- function(str_v) {
match <- str_match(str_v, date_regex)
left_number <- hlp_convert_single_date(match[, 2:5])
right_number <- hlp_convert_single_date(match[, 6:9])
res_number <- map2_dbl(
left_number,
right_number,
~ mean(c(.x, .y), na.rm = TRUE)
)
return(res_number)
}
test_cols_dates %>%
format_dates() %>%
convert_dates()
#' Parse dates from a dataframe.
#' @param df Dataframe containing date columns to parse.
#' @return Dataframe with dates parsed.
#' @see https://stackoverflow.com/questions/45947787/create-new-variables-with-mutate-at-while-keeping-the-original-ones
parse_dates_df <- function(df) {
# Columns that we will compute on
cols <- c("date_active", "date_begin", "date_end")
res_df <-
df %>%
# 1. Apply the functions to every columns we selected.
mutate(across(
.cols = all_of(cols),
.fns = list(
str = format_dates,
num = ~ format_dates(.) %>% convert_dates()
),
.names = "{col}_{fn}"
)) %>%
# 2. Drop original colums to replace them with their numeric equivalent
select(-all_of(cols)) %>%
rename_at(
paste0(cols,"_num"),
~ cols
) %>%
# 3. Regroup columns by type
relocate(all_of(cols), .before = "date_active_str")
return(res_df)
}
phi_authors_df <- phi_authors_df %>%
parse_dates_df()
tlg_authors_df <- tlg_authors_df %>%
parse_dates_df()
# Beware, the code writing is not great here,
# it has been done quickly.
# To improve, one could look at dyplr::summarise(), or skimr
#' @param df Dataframe.
#' @returns NULL. It prints the summary.
print_date_summary <- function(df) {
tmp_cols <- list(
df$date_active,
df$date_begin,
df$date_end
) %>%
transpose()
tibble(
col_name = c("date_active", "date_begin", "date_end"),
min = pmap_dbl(tmp_cols, min, na.rm = TRUE),
max = pmap_dbl(tmp_cols, max, na.rm = TRUE)
)
}
print_date_summary(phi_authors_df)
Les auteurs latins que nous avons se répartissent globalement du VIe siècle avant notre ère au VIe siècle après.
print_date_summary(tlg_authors_df)
Les auteurs grecs que nous avons se répartissent globalement du VIIIe siècle avant notre ère au XVIe siècle après.
Pour s’assurer de l’homogénéité des données, tout en gagnant en généralité, nous n’allons garder en matière de lieu que le pays, en laissant de côté des indications plus précises comme la ville. Nous ne gardons également arbitrairement qu’un seul pays lorsque plusieurs sont indiqués.
#' Format places.
#' Only the most-left string, uninterrupted by a comma, is kept.
#' @param df Dataframe
#' @return Dataframe
format_places_df <- function(df) {
res_df <-
df %>%
rowwise() %>%
mutate(
place = place %>%
# 1. Split by comma or similar signs that are also used.
# Comma and slash are meaningful,
# but dots or double back-slashs seem to be mistakes.
str_split( ",|/|\\.|\\\\") %>%
# 2. Keep only the last part
unlist() %>%
last() %>%
# 3. Clean the result for homogeneity
str_trim() %>%
str_to_title() %>%
str_replace_all("\\?", "") %>%
# 4. Fix some writing errors.
str_replace_all(c(
"Liby$" = "Libya",
"Cyrpus" = "Cyprus"
))
) %>%
ungroup()
return(res_df)
}
# Check that final places are unique.
test_places <- function(df) {
df %>%
format_places_df() %>%
distinct(place) %>%
arrange(place) %>%
pull(place)
}
test_places(phi_authors_df)
test_places(tlg_authors_df)
phi_authors_df <- format_places_df(phi_authors_df)
tlg_authors_df <- format_places_df(tlg_authors_df)
phi_authors_df %>%
distinct(place) %>%
arrange(place) %>%
pull() %>%
knit_print()
[1] "Africa" "Algeria" "Egypt" "France" "Italy" "Lebanon" "Libya" "North Africa" "Spain" "Tunisia" "Turkey" NA
phi_authors_df %>%
summarise(
n = n(),
na_n = sum(is.na(place)),
na_rate = round(na_n / n, 3)
)
tlg_authors_df %>%
distinct(place) %>%
arrange(place) %>%
pull() %>%
knit_print()
[1] "Albania" "Anatolia" "Armenia" "Bulgaria" "Cous" "Crete" "Cyprus" "Delos" "Egypt" "Elaita"
[11] "Ephesos" "Epirus" "France" "Greece" "Iasensis" "Incerta" "Ionia" "Iran" "Iraq" "Israel"
[21] "Italy" "Jordan" "Lebanon" "Lesbos" "Libya" "Lydia" "Macedonia" "Manesium" "Mauretania" "Mendesicus"
[31] "Mytilene" "Olynthus" "Palestine" "Pontus Euxinus" "Rhodes" "Romania" "Rome" "Sardianus" "Scythia" "Sicily"
[41] "Sicyon" "Syria" "Syrian Antioch" "Thrace" "Tunisia" "Turkey" "Ukraine" NA
tlg_authors_df %>%
summarise(
n = n(),
na_n = sum(is.na(place)),
na_rate = round(na_n / n, 3)
)
On ajoute finalement les genres, depuis les données du CLTK. Lorsqu’il y aura plusieurs genres pour un même auteur, on ne conservera que le premier genre dans l’ordre alphabétique.
tmp_tlg_epithets_df <-
tlg_epithets_raw_df %>%
unnest(cols = code) %>%
mutate(code = code %>%
unlist() %>%
parse_number()
) %>%
distinct(code, .keep_all = TRUE)
tlg_authors_df <- tlg_authors_df %>%
left_join(tmp_tlg_epithets_df, by = "code") %>%
relocate(genre, .after = name)
remove(tmp_tlg_epithets_df)
tlg_authors_df %>%
distinct(genre) %>%
arrange(genre) %>%
pull() %>%
knit_print()
[1] "Alchemistae" "Apologetici" "Astrologici" "Astronomici" "Atticistae" "Biographi"
[7] "Bucolici" "Choliambographi" "Chronographi" "Comici" "Doxographi" "Elegiaci"
[13] "Epici/-ae" "Epigrammatici/-ae" "Epistolographi" "Geographi" "Geometri" "Gnomici"
[19] "Gnostici" "Grammatici" "Historici/-ae" "Iambici" "Lexicographi" "Lyrici/-ae"
[25] "Mathematici" "Mechanici" "Medici" "Mimographi" "Musici" "Mythographi"
[31] "Nomographi" "Onirocritici" "Oratores" "Paradoxographi" "Parodii" "Paroemiographi"
[37] "Periegetae" "Philologi" "Philosophici/-ae" "Poetae" "Poetae Didactici" "Poetae Medici"
[43] "Poetae Philosophi" "Polyhistorici" "Rhetorici" "Scriptores Ecclesiastici" "Scriptores Erotici" "Scriptores Fabularum"
[49] "Sophistae" "Tactici" "Theologici" "Tragici" NA
Les données obtenues directement depuis le fichier du CLTK paraissent ne pas avoir d’irrégularités et donc ne pas nécessiter de nettoyage supplémentaire.
tlg_authors_df %>%
summarise(
n = n(),
na_n = sum(is.na(genre)),
na_rate = round(na_n / n, 3)
)
tlg_authors_df %>% slice(31:40)
Notons néanmoins qu’il aurait été plus efficace de récupérer l’information des genres directement sur le nom des auteurs : en calculant les valeurs manquantes, on voit qu’il nous en manque plus d’un tiers, et en consultant le contenu des tableaux (ci-dessus un extrait), on s’aperçoit que bien souvent l’épithète qui apparaît pourtant dans le nom n’apparaît pas dans la colonne générée à partir des données du CLTK.
Pour terminer et faciliter les analyses graphiques suivantes, nous allons créer deux nouvelles variables :
l’initiale : pour éviter d’avoir à traiter des milliers de noms d’auteurs alors que nous nous intéresserons à un possible classement alphabétique, nous pourrons ainsi ne retenir que leur initiale. Cela nous permettra également d’afficher des couleurs en fonction des initiales, celles-ci constituant une variable catégorielle ;
la date : on prendra arbitrairement le plus souvent le siècle d’activité comme date de référence, puis nous en dupliquons la colonne pour y accéder plus facilement.
De plus, nous supprimons les caractères indésirables au début du nom des auteurs, pour être sûrs de récupérer l’initiale correctement et d’être conformes à l’ordre alphabétique.
# Nettoyage de parenthèses diverses pour le fichier TLG
tlg_authors_df$name <- gsub("(<|\\[|\\()", "", tlg_authors_df$name)
# Création d'une colonne des initiales des auteurs
tlg_authors_df$init <- substring(tlg_authors_df$name,1,1)
phi_authors_df$init <- substring(phi_authors_df$name,1,1)
# Réorganisation de l'ordre des colonnes
tlg_authors_df <- tlg_authors_df %>%
relocate(init, .before = name)
phi_authors_df <- phi_authors_df %>%
relocate(init, .before = name)
# Ajout d'une colonne "date" qui copie "date_active", par commodité
tlg_authors_df <- tlg_authors_df %>%
mutate(date = date_active, .before = date_active)
phi_authors_df <- phi_authors_df %>%
mutate(date = date_active, .before = date_active)
Pour savoir plus précisément ce à quoi correspondent chacune des variables obtenues, rendez-vous au début de la section !
phi_authors_df
skim(phi_authors_df)
| Name | phi_authors_df |
| Number of rows | 358 |
| Number of columns | 11 |
| _______________________ | |
| Column type frequency: | |
| character | 6 |
| numeric | 5 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| init | 12 | 0.97 | 1 | 1 | 0 | 19 | 0 |
| name | 12 | 0.97 | 5 | 60 | 0 | 346 | 0 |
| place | 100 | 0.72 | 5 | 12 | 0 | 11 | 0 |
| date_active_str | 77 | 0.78 | 5 | 14 | 0 | 27 | 0 |
| date_begin_str | 170 | 0.53 | 3 | 13 | 0 | 121 | 0 |
| date_end_str | 156 | 0.56 | 3 | 13 | 0 | 136 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| code | 0 | 1.00 | 855.19 | 1061.21 | 2 | 445.25 | 643 | 1013.25 | 9969 | ▇▁▁▁▁ |
| date | 77 | 0.78 | -21.85 | 174.43 | -550 | -150.00 | -100 | 100.00 | 550 | ▁▅▇▂▁ |
| date_active | 77 | 0.78 | -21.85 | 174.43 | -550 | -150.00 | -100 | 100.00 | 550 | ▁▅▇▂▁ |
| date_begin | 170 | 0.53 | -35.12 | 146.09 | -529 | -116.50 | -75 | 39.62 | 483 | ▁▃▇▂▁ |
| date_end | 156 | 0.56 | 23.72 | 151.75 | -430 | -67.00 | -11 | 96.50 | 565 | ▁▇▇▂▁ |
tlg_authors_df
skim(tlg_authors_df)
| Name | tlg_authors_df |
| Number of rows | 2176 |
| Number of columns | 12 |
| _______________________ | |
| Column type frequency: | |
| character | 7 |
| numeric | 5 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| init | 40 | 0.98 | 1 | 1 | 0 | 24 | 0 |
| name | 40 | 0.98 | 4 | 65 | 0 | 2033 | 0 |
| genre | 860 | 0.60 | 6 | 24 | 0 | 52 | 0 |
| place | 701 | 0.68 | 4 | 14 | 0 | 47 | 0 |
| date_active_str | 175 | 0.92 | 4 | 15 | 0 | 110 | 0 |
| date_begin_str | 930 | 0.57 | 3 | 13 | 0 | 343 | 0 |
| date_end_str | 914 | 0.58 | 3 | 13 | 0 | 361 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| code | 0 | 1.00 | 1565.75 | 1250.89 | 1 | 593.75 | 1425.5 | 2186.25 | 9023 | ▇▃▁▁▁ |
| date | 175 | 0.92 | -56.06 | 389.59 | -800 | -350.00 | -125.0 | 200.00 | 1550 | ▇▇▆▁▁ |
| date_active | 175 | 0.92 | -56.06 | 389.59 | -800 | -350.00 | -125.0 | 200.00 | 1550 | ▇▇▆▁▁ |
| date_begin | 930 | 0.57 | -96.66 | 378.54 | -800 | -399.00 | -199.0 | 116.50 | 1465 | ▆▇▃▁▁ |
| date_end | 914 | 0.58 | -6.50 | 377.31 | -680 | -300.00 | -100.0 | 200.00 | 1535 | ▇▇▃▁▁ |
Pour trouver les logiques internes de classement, nous allons procéder progressivement, en analysant successivement les différentes variables en fonction de la variable à expliquer, l’identifiant.
Nous commencerons par analyser les variables deux à deux, en projetant les auteurs selon un nuage de points. L’identifiant, variable à expliquer, sera systématiquement projeté en abscisse, et la variable potentiellement explicative en ordonnée.
Puis nous chercherons à saisir des sous-logiques internes en ajoutant une troisième variable, ce qui correspond à une “troisième dimension” sur nos graphiques, que nous matérialiserons par de la couleur.
Si nécessaire, au fil de nos analyses, nous pourrons revenir en “deux dimensions” si cela paraît pertinent – éventuellement en colorant une des dimensions pour davantage de clarté visuelle –, par exemple sur une séquence particulière – car il se pourrait bien que nous découvrions l’existence de “séquences” particulières…
À chaque fois, on analysera les auteurs grecs, puis les auteurs latins. Au fur et à mesure, on saisira les logiques sous-jacentes aux classifications TLG et PHI.
La classification qui paraîtrait la plus intuitive est celle de l’ordre alphabétique. Voyons donc si les codes sont corrélés ou non aux noms des auteurs projetés par ordre alphabétique.
tlg_authors_df %>%
ggplot(aes(code, fct_reorder(name, desc(name)))) +
geom_point() +
labs(title = "Identifiants TLG et noms", x = "code", y = "name")
Ce premier graphique présente un premier problème : les données sont très nombreuses, si bien que les résultats sont peu clairs. On observe néanmoins que la majorité des codes se trouvent entre 0 et 2500, et la grande majorité entre 0 et 5000. On pourrait donc se concentrer sur cette portion, et masquer les noms de sorte à ce que le graphique puisse davantage s’étaler.
tlg_authors_df %>%
filter(code < 5000) %>%
ggplot(aes(code, fct_reorder(name, desc(name)))) +
geom_point() +
labs(title = "Identifiants TLG et noms (0-5000)", x = "code", y = "name") +
scale_x_continuous(breaks = round(seq(0, 5000, by = 200),1)) +
theme(
axis.text.y = element_blank(),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
panel.grid.major.x = element_line(size = 0.5, colour = "grey")
)
On observe assez clairement des lignes droites. Cela ressemble à des séries cohérentes de noms classés par ordre alphabétique sur certaines portions. Il faudrait donc découper les codes TLG en plusieurs séquences successives pour en analyser la logique interne.
On observe notamment que les droites qui apparaissent se situent avant le code 1800 : il semblerait donc que les codes ultérieurs obéissent à une autre logique que la logique alphabétique, alors qu’avant 1800 il y aurait pu avoir des “ajouts par vagues successives” à la liste des identifiants, chaque vague étant par ordre alphabétique.
Intéressons-nous plus en détail à la séquence avant 1800.
# Première tentative de créer un vecteur à partir des codes,
# pour les diviser en plusieurs séquences,
# et en tirer des informations plus facilement.
#code_authors_tlg <-
# as_tibble(tlg_authors_df) %>%
# slice(code) %>%
# unlist(., use.names = FALSE)
#split(code_authors_tlg, 1:2545)
tlg_authors_df %>%
filter(code < 1800) %>%
ggplot(aes(code, fct_reorder(name, desc(name)))) +
geom_point() +
labs(title = "Identifiants TLG et noms (0-1800)", x = "code", y = "name") +
scale_x_continuous(breaks = round(seq(0, 1800, by = 100),1)) +
theme(
axis.text.y = element_blank(),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
panel.grid.major.x = element_line(size = 0.5, colour = "grey")
)
labs(title = "Identifiants TLG et noms (0-5000)")
$title
[1] "Identifiants TLG et noms (0-5000)"
attr(,"class")
[1] "labels"
On observe :
De plus, on remarque plusieurs éléments :
Même sur cette portion, il y a plusieurs séquences d’identifiants qui paraissent ne pas suivre la logique alphabétique (en particulier entre 0 et 100, entre 200 et 400, entre 520 et 650). Ces séquences pourraient suivre une autre logique, ou bien ne correspondre à rien d’identifiable facilement – il pourrait s’agir d’une attribution “au hasard”, ou correspondre à une entrée des identifiants “au fil de l’eau” sans logique systématique pré-établie, par exemple au fur et à mesure que de nouveaux auteurs enrichissaient le corpus TLG.
Même sur les séquences où l’ordre alphabétique paraît significatif, il y a parfois quelques “points aberrants”, éloignés de la droite qui se dessine : ils semblent ne pas suivre la logique de la séquence d’identifiants dans laquelle ils s’insèrent. Le plus vraisemblable, c’est alors que le nom que nous avons dans nos données ne corresponde pas au nom qui a été utilisé pour attribuer l’identifiant.
Enfin, on remarque un aspect qui pouvait déjà être aisément constaté auparavant : il existe des “identifiants” manquants, auxquels aucun auteur ne correspond. Ce phénomène semble apparaître également au sein même des séries alphabétiques. Par exemple :
tlg_authors_df %>% filter(code >= 840 & code <= 850)
On observe ici qu’entre les identifiants 840 et 850, on ne trouve en fait que deux identifiants : le 845 et le 848. Les identifiants 840, 841, 842, 843, 844, 846, 847, 849 et 850 sont “manquants”.
Ce dernier phénomène paraît difficile à expliquer. Nous voyons trois hypothèses, néanmoins un peu légères. D’une part, ces “sauts” pourraient avoir été effectués au moment même de l’attribution, et alors il pourrait s’agir d’erreurs ou d’ajouts intempestifs au sein d’une série plus longue. D’autre part, ces “sauts” pourraient être le résultat de manipulations ultérieures : il se pourrait que les auteurs qui possédaient ces identifiants aient été “réattribués”, ou même tout simplement supprimés, pour une raison ou pour une autre. Enfin, la dernière hypothèse repose sur une variable essentielle qui nous manque ici : les différentes phases de développement du TLG et de Perseus. Une fois qu’un identifiant a été attribué, il ne peut être changé, même si les critères d’attributions changent de leur côté.
Passons aux auteurs latins.
phi_authors_df %>%
ggplot(aes(code, fct_reorder(name, desc(name)))) +
geom_point() +
labs(title = "Identifiants PHI et noms", x = "code", y = "name")
On voit que la grande majorité des identifiants sont inférieurs à 2500 : concentrons-nous sur cette portion.
phi_authors_df %>%
filter(code < 2500) %>%
ggplot(aes(code, fct_reorder(name, desc(name)))) +
geom_point() +
labs(title = "Identifiants PHI et noms (0-2500)", x = "code", y = "name") +
scale_x_continuous(breaks = round(seq(0, 2500, by = 100),1)) +
theme(
axis.text.y = element_blank(),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
panel.grid.major.x = element_line(size = 0.5, colour = "grey")
)
Contrairement aux auteurs grecs du TLG, les résultats paraissent cette fois beaucoup moins évidents, et la corrélation entre l’identifiant et la place du nom dans l’ordre alphabétique est un peu douteuse. Tout au plus, on pourrait penser qu’il existe une petite tendance de ~400 à ~700, voire entre ~800 et ~1050. Mais il semblerait que la classification obéisse essentiellement à d’autres logiques, que nous allons désormais tenter d’explorer.
Après l’ordre alphabétique, une autre classification qui paraîtrait assez intuitive est une classification chronologique par dates. Nous allons donc voir dans quelle mesure les codes TLG et PHI sont explicables par l’époque des auteurs, puis nous croiserons ensuite, dans la partie suivante, la date avec d’autres variables.
# Première étude en noir et blanc
ggplot(tlg_authors_df, aes(x = code, y = date)) +
geom_point() +
labs(title = "Identifiants TLG et dates")
À première vue, la variable “date” n’explique pas la variable “code” du TLG. Les 2600 premiers identifiants se concentrent avant 2500, et l’on constate quelques “cheminées” aux environs de 3000, 4000 et 9000 concernant surtout les auteurs d’après 500 CE.
ggplot(phi_authors_df, aes(x = code, y = date)) +
geom_point() +
labs(title = "Identifiants PHI et dates")
Concernant les auteurs latins, on constate pour le code PHI une corrélation assez forte entre la progression du code et celle des dates. Un code culmine à 9500 et trois autres auteurs dépassant 9000 ne sont pas datés.
Regardons de plus près la séquence de 0 à 3000.
tmp_phi_3000 <-
phi_authors_df %>%
filter(code < 3000)
#' Make a linear model, print its infos and components, and plot its equivalent.
#' rq: Here we try using `tibble()` to make quosures work.
#' There may be a neater way.
#' rq: We need to explicitly make things printed again,
#' otherwise only a table at the end of the function will be printed.
#' Thus, we need to 'hide' chunk results to avoid printing of raw console output.
#' @see https://stackoverflow.com/questions/42024921/why-does-kable-not-print-when-used-within-a-function-in-rmarkdown
#' @param var String of the variable name to test in the linear model.
#' @param var_strin String to display in the plot name.
#' @return Things to print.
phi_display_lm <- function(var, var_str) {
data_df <- tmp_phi_3000
lm_df <-
tibble(
lm_components = broom::tidy(lm(code ~ !!sym(var), data_df)),
lm_infos = broom::glance(lm(code ~ !!sym(var), data_df))
)
lm_comps_print <- lm_df %>% pull(lm_components) %>% knit_print()
lm_infos_print <- lm_df %>% pull(lm_infos) %>% slice_head() %>% knit_print()
lm_plot <- data_df %>%
ggplot(aes(x = code, y = !!sym(var))) +
geom_point() +
geom_smooth(method = "lm") +
scale_y_continuous(
limits = c(-500, 600),
breaks = seq(-500, 600, by = 100)
) +
labs(title = paste0("Identifiants PHI et ",var_str))
return(list(
lm_comps_print,
lm_infos_print,
lm_plot
))
}
phi_display_lm("date", "dates d'activité")
En y regardant de plus près on voit bien, sur les identifiants entre 0 et 3000, une corrélation linéaire significative et sans ambiguïté (\(p\mbox{-}valeur \lll 0.001\)) entre la valeur de l’identifiant et la date, ou le siècle, d’activité : un simple modèle linéaire (\(code_{i} = \beta_{0} + \beta_{1} \times date_{i} + \epsilon_{i}\)) explique ainsi les trois quarts de la variance (\(R^{2} \approx 0.76\)).
Rappelons que nous avons ici utilisé une seule des trois dates qui étaient à notre disposition : voyons si les deux autres sont davantage significatives.
phi_display_lm("date_begin", "dates de commencement")
phi_display_lm("date_end", "dates de fin")
En matière de calculs statistiques, il ne paraît pas évident de tirer des conclusions. En revanche, du point de vue de l’analyse graphique, les dates de “fin” semblent, au moins autour de l’identifiant 500, former un carré comme si les auteurs morts au cours du premier siècle avant notre ère faisaient tous, ou presque, partie d’une même catégorie. On pourrait encore affiner les analyses de cette catégorisation.
remove(tmp_phi_3000)
D’autres variables interviennent-elles dans la numérotation des codes ? En sus des dates, nous allons examiner deux autres variables, l’initiale du nom – en croisant donc la question de l’ordre alphabétique que nous avons déjà évoquée – et le genre littéraire. Ces variables seront exprimés par des couleurs.
Les couleurs par défaut de ggplot étant plutôt fades et peu différenciées, rendant la lecture difficile, nous créerons également une palette passant graduellement du rouge au bleu, en passant respectivement par l’orangé, le jaune et le vert (il faut compter près de 26 lettres).
# Création d'une palette de couleurs de type rosace,
# en jouant sur les codes #RRVVBB, chaque chiffre de 0 à F.
couleurs <- c(
"#990000", "#cc0000", "#ff0000", "#ff3300",
"#ff6600", "#ff9900", "#ffcc00", "#ffff00",
"#ccff00", "#99ff00", "#66ff00", "#33ff00",
"#00ff00", "#00ff33", "#00ff66", "#00ff99",
"#00ffbb", "#00ffff", "#00ccff", "#0099ff",
"#0066ff", "#0033ff", "#0000ff", "#0000cc",
"#000099"
)
Nous reprenons les deux graphiques précédents projetant les identifiants selon les dates (TLG et PHI), en colorant cette fois les points selon les initiales des auteurs.
ggplot(tlg_authors_df, aes(x = code, y = date, colour = init)) +
geom_point() +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants TLG, dates et initiales")
De longs traits verticaux de couleurs apparaissent, mais un cadrage est à réaliser, notamment pour les codes entre 1150 et 1800.
ggplot(tlg_authors_df, aes(x = code, y = date, colour = init)) +
geom_point() +
xlim(1150, 1800) +
ylim(-750, 500) +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants TLG, dates et initiales (1150-1800)")
Il apparaît clairement que les numéros de code entre 1150 et 1800 suivent les initiales du noms des auteurs grecs. Le graphique suivant examine les codes entre 1800 et 2500. En revanche, la date paraît ne jouer aucune importance : on retrouve, pour chaque même date, des auteurs tout le long de la séquence d’identifiants considérée.
ggplot(tlg_authors_df, aes(x = code, y = date, colour = init)) +
geom_point() +
xlim(1800, 2500) +
ylim(-750, 500) +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants TLG, dates et initiales (1800-2500)")
L’observation qui s’applique entre 1150 et 1800 ne se retrouve pas entre 1800 et 2500, même si l’on retrouve quelques lignes ascendantes, peut-être fortuites.
Observons désormais les autres séquences que nous avions distinguées lorsque nous avons projeté les identifiants en fonction des noms.
#' Plot code by date and init,
#' for a sequence between two given values,
#' from `phi_authors_df_nopr`.
#' @param start Number. Code to start with
#' @param end Number. Code to end with.
tlg_plot_code_date_init_seq <- function(start, end) {
tlg_authors_df %>%
filter(code >= start, code <= end) %>%
drop_na(date, init) %>%
ggplot(aes(x = code, y = date, colour = init)) +
geom_point() +
#ylim(-600, 300) +
scale_colour_manual(values = couleurs) +
labs(title = paste0("Identifiants TLG, dates et initiales (",start,"-",end,")"))
}
tlg_plot_code_date_init_seq(100, 200)
tlg_plot_code_date_init_seq(390, 550)
tlg_plot_code_date_init_seq(650, 750)
tlg_plot_code_date_init_seq(750, 1100)
Comme nous l’avions remarqué, il existe en effet une régularité. Néanmoins, les dates ne semblent jouer aucun rôle sur ces séquences, si ce n’est que celles-ci s’étalent temporellement plus ou moins sur ces intervalles :
On applique le principe des points de couleur selon l’initiale pour les auteurs latins.
# Auteurs latins.
ggplot(phi_authors_df, aes(x = code, y = date, colour = init)) +
geom_point() +
xlim(0, 2500) +
ylim(-350, 600) +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants PHI, dates et initiales")
Si le code est corrélé à la date, ce qui apparaissait déjà plus haut, le nom ne semble avoir aucun rôle dans la numérotation.
Il nous avait néanmoins semblé que quelque chose comme une suite alphabétique pourrait bien apparaître au moins entre les identifiants 400 et 700. Restreignons donc l’observation plus spécifiquement à cette séquence. Étant donné que cette séquence fait apparaître des auteurs d’une période similaire – le premier siècle avant notre ère –, nous allons pour un instant revenir en deux dimensions en supprimant la variable date et ainsi mieux voir la répartition des noms.
ggplot(phi_authors_df, aes(x = code, y = fct_reorder(name, desc(name)), colour = init)) +
geom_point() +
xlim(400, 750) +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants PHI et noms (400-750)", x = "code", y = "name")
Une ligne paraît vaguement se distinguer, mais de très nombreux auteurs semblent ne pas entrer dans cette logique. C’est possible car le nom qui apparaît dans notre tableau n’est pas conforme au nom utilisé dans la classification.
On remarque en effet que les noms tels que nous les avons récupérés commencent souvent par le “praenomen”. Il est facile d’ôter les prénoms représentés par une initiale suivie d’un point. On crée pour cela un tableau intermédiaire.
phi_authors_df_nopr <- phi_authors_df
phi_authors_df_nopr$name <- gsub("[A-Z][a-z]{0,2}\\. ", "", phi_authors_df_nopr$name)
phi_authors_df_nopr$init <- substring(phi_authors_df_nopr$name,1,1)
Un travail plus systématique aurait consisté à observer sur le site de PHI ce qui y est considéré comme étant le nom principal de chaque auteur (apparaissant en gras) pour garder celui-ci comme nom de référence. Il aurait été possible de récupérer ces données depuis internet par exemple avec la bibliothèque rvest.
Pour l’heure, nous nous contenterons de cette première mise en forme.
ggplot(phi_authors_df_nopr, aes(x = code, y = date, colour = init)) +
geom_point() +
xlim(0, 2500) +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants PHI, dates et initiales (sans praenomen)")
Il est difficile de distinguer les choses à ce niveau. De nouveau, revenons à une représentation en deux dimensions en supprimant la variable “date”, puis nous nous focaliserons sur certaines séquences, chacune correspondant à une période.
phi_authors_df_nopr %>%
filter(code < 2500) %>%
drop_na(name, init) %>%
ggplot(aes(x = code, y = fct_reorder(name, desc(name)), colour = init)) +
geom_point() +
scale_colour_manual(values = couleurs) +
labs(title = "Identifiants PHI et noms (sans praenomen)", x = "code", y = "name")
Des lignes apparaissent effectivement bien clairement ! Regardons les séquences de plus près.
#' Plot code by name,
#' for a sequence between two given values,
#' from `phi_authors_df_nopr`.
#' @param start Number. Code to start with
#' @param end Number. Code to end with.
phi_plot_code_name_seq <- function(start, end) {
phi_authors_df_nopr %>%
filter(code >= start, code <= end) %>%
drop_na(name, init) %>%
ggplot(aes(x = code, y = fct_reorder(name, desc(name)), colour = init)) +
geom_point() +
scale_colour_manual(values = couleurs) +
labs(title = paste0("Identifiants PHI et noms (sans praenomen, ",start,"-",end,")"), x = "code", y = "name") +
theme(axis.text.y = element_blank())
}
phi_plot_code_name_seq(0, 200)
phi_plot_code_name_seq(400, 750)
phi_plot_code_name_seq(750, 1200)
phi_plot_code_name_seq(1200, 1400)
Les résultats apparaissent bien avec clarté.
Une dernière manière de se représenter l’hypothèse et ses résultats serait de colorer cette fois non plus les initiales mais les dates : on devrait observer que les différentes séquences sont chacune d’une teinte particulière.
phi_authors_df_nopr %>%
filter(code < 2500) %>%
drop_na(name, date) %>%
ggplot(aes(code, fct_reorder(name, desc(name)), colour = date)) +
geom_point() +
labs(title = "Identifiants PHI, noms et dates", y = "name") +
scale_colour_gradientn(colours = couleurs) +
theme(axis.text.y = element_blank())
C’est effectivement ce que l’on observe !
Il semblerait donc que les auteurs latins soient, dans la classification PHI, rangés d’abord par date, et ensuite de manière secondaire, sur chaque période, par ordre alphabétique du nom.
Le nom ne commence alors manifestement pas par le praenomen. Les points aberrants par rapport aux droites qui se dessinent sont vraisemblablement des auteurs pour lesquels le nom que nous avons dans notre tableau ne correspond pas au nom utilisé lors de l’attribution de l’identifiant.
Pour distinguer plus clairement les périodes, nous devrions faire des recherches supplémentaires, afin de savoir précisément selon quel(s) critère(s) certains auteurs sont rattachés à une période et non à la précédente ou à la suivante : s’agit-il du siècle correspondant à la date de naissance ? à la date de mort ? à la période principale de production littéraire ? Ou est-ce un découpage d’un autre ordre, selon des périodes historiques ? Il faudrait pour cela délimiter précisément les séquences, et on pourrait ensuite regarder les auteurs à leurs extrémités par exemple, afin de saisir ce qui fait passer d’une séquence à une autre.
Il faut noter enfin que, comme nous l’avons déjà relevé pour les auteurs grecs, il existe de nouveau des “trous” dans les séquences : ces “sauts” dans la numérotation restent en l’état quelque peu mystérieux.
Pour les auteurs grecs, cela reste encore peu clair. Si certaines séries alphabétiques apparaissent, on ne sait pas pourquoi elles apparaissent dans cet ordre-là, et il existe également des séquences entières qui semblent ne pas correspondre à l’ordre alphabétique ni à l’ordre chronologique.
Essayons donc de chercher une logique à partir des genres littéraires.
tlg_authors_df %>%
filter(code < 5000) %>%
ggplot(aes(x = code, y = fct_reorder(genre, desc(genre)))) +
geom_point() +
labs(title = "Identifiants TLG et genres littéraires", y = "genre")
Un premier graphique en deux dimensions ne donne pas grand-chose : à première vue, chaque genre peut se répartir tout du long de la numérotation.
Supprimons les genres contenant moins de dix auteurs pour y voir plus clair.
tlg_authors_df %>%
filter(code < 5000) %>%
group_by(genre) %>%
mutate(genre_n = n()) %>%
ungroup() %>%
filter(genre_n > 10) %>%
ggplot(aes(x = code, y = fct_reorder(genre, desc(genre)))) +
geom_point() +
labs(title = "Identifiants TLG et genres littéraires (plus de dix auteurs)", y = "genre")
À la rigueur, on peut relever que les orateurs sont concentrés au début de la numérotation, et les “Poetae” autour de 2600.
Néanmoins, on a sur ce graphique du mal à distinguer la densité des identifiants et leurs regroupements. Par exemple, on a du mal à faire la différence entre les petites masses très denses et les points uniques ou presque, et on peine à appréhender à quel point certaines lignes sont continues ou non. Ajustons les paramètres graphiques pour y voir plus clair.
tlg_authors_df %>%
filter(code < 5000) %>%
group_by(genre) %>%
mutate(genre_n = n()) %>%
ungroup() %>%
filter(genre_n > 10) %>%
ggplot(aes(x = code, y = fct_reorder(genre, desc(genre)))) +
geom_jitter(alpha = 0.2, shape = 16, width = 0, height = 0.2) +
labs(title = "Identifiants TLG et genres littéraires (plus de dix auteurs)", y = "genre")
Désormais, tout est davantage lisible : on observe des ajouts “par grappe”, en particulier pour les “Comici”, “Tragici”, “Elegiaci”, et également aussi pour les “Geographi”, “Lyrici/-ae”, “Medici”, “Oratores”, “Poetae”, “Rhetorici”, …
Jetons également un œil aux genres mis de côté, à savoir ceux qui contiennent dix auteurs ou moins.
tlg_authors_df %>%
filter(code < 5000) %>%
group_by(genre) %>%
mutate(genre_n = n()) %>%
ungroup() %>%
filter(genre_n <= 10) %>%
ggplot(aes(x = code, y = fct_reorder(genre, desc(genre)))) +
geom_jitter(alpha = 0.3, shape = 16, width = 0, height = 0.1) +
labs(title = "Identifiants TLG et genres littéraires (dix auteurs et moins)", y = "genre")
On observe des petits regroupements, qui ne sont pas systématiques : des “Paradoxographi” surtout, des “Bucolici” également, des “Doxographi”, peut-être aussi des “Mythographi”…
Essayons désormais des analyses en trois dimensions, en couleurs.
length(unique(tlg_authors_df$genre))
[1] 53
On trouve 52 genres (53 sans un NA) dans le fichier des auteurs TLG, qu’il est difficile de représenter clairement en couleurs. Nous gardons huit genres parmi les plus représentés. Ceci est une démarche exploratoire : l’idéal serait évidemment de produire plusieurs graphiques ; rappelons en outre que, comme il est expliqué plus haut, lorsque plusieurs genres s’appliquent à un auteur, nous avons gardé le premier apparaissant par ordre alphabétique.
count_tlg_genre <-
tlg_authors_df %>%
count(genre, sort = TRUE)
knit_print(count_tlg_genre[1:11,])
De cette liste, nous avons éliminé les “Historici/-ae” trop dispersés apportant de la confusion dans la lecture, et les “Scriptores Ecclesiastici”, assez dispersés également, même s’ils n’apparaissent que tardivement.
# On applique 8 genres avec une sélection de couleurs
tlg_authors_df %>%
filter(genre %in% c(
"Philosophici/-ae", "Comici", "Tragici", "Grammatici",
"Epici/-ae", "Lyrici/-ae", "Rhetorici", "Medici"
)) %>%
ggplot(aes(x = code, y = date, colour = genre)) +
geom_point() +
xlim(0, 2800) +
ylim(-350, 600) +
scale_colour_manual(values = couleurs[c(1, 4, 6, 9, 11, 15, 20, 25)]) +
labs(title = "Identifiants TLG, dates et genres littéraires")
De ce premier graphique, il apparaît que les philosophes sont dispersés, mais restent en dehors de la zone des comiques et des tragiques. Nous observons aussi un longue ligne verticale représentant les médecins. Cette observation est plus parlante si nous ne gardons plus que ces quatre derniers genres.
tlg_authors_df %>%
filter(genre %in% c(
"Philosophici/-ae", "Comici", "Tragici", "Medici"
)) %>%
ggplot(aes(x = code, y = date, colour = genre)) +
geom_point() +
xlim(0, 2800) +
ylim(-350, 600) +
scale_colour_manual(values = couleurs[c(1, 6, 15, 25)]) +
labs(title = "Identifiants TLG, dates et quatre genres")
Il est curieux de constater que la comédie semble encadrée par la tragédie.
# Zoom sur les codes de comiques, médecins et tragiques.
tlg_authors_df %>%
filter(genre %in% c("Comici", "Tragici", "Medici")) %>%
ggplot(aes(x = code, y = date, colour = genre)) +
geom_point() +
xlim(0, 2000) +
ylim(-350, 600) +
scale_colour_manual(values = couleurs[c(1, 6, 25)]) +
labs(title = "Identifiants TLG, dates et trois genres")
En fait, on observe que les comiques regroupés de la sorte correspondent à la séquence que nous avions relevé, entre ~390 et ~520, dans notre première analyse (2.1. Les identifiants par les noms).
tlg_authors_df %>%
filter(genre %in% c("Comici", "Tragici", "Medici")) %>%
ggplot(aes(x = code, y = fct_reorder(name, desc(name)), colour = genre)) +
geom_point() +
xlim(0, 1000) +
scale_colour_manual(values = couleurs[c(1, 6, 25)]) +
labs(title = "Identifiants TLG, noms et trois genres", y = "name")
On voit clairement apparaître les comiques rangés par ordre alphabétique ! Si une ligne verticale apparaît avec les tragiques, entre ~260 et ~380, voire entre ~620 et ~750 pour les médecins, ces deux catégories paraissent néanmoins ne pas être rangées par ordre alphabétique.
Tentons une dernière série de graphiques, représentant les identifiants en fonction des noms, avec les genres en couleurs.
tmp_tlg_authors_genres <-
tlg_authors_df %>%
drop_na(genre) %>%
count(genre) %>%
arrange(desc(n)) %>%
pull(genre)
tlg_authors_df %>%
filter(genre %in% tmp_tlg_authors_genres[0:8]) %>%
ggplot(aes(x = code, y = fct_reorder(name, desc(name)), colour = genre)) +
geom_point() +
xlim(0, 5000) +
scale_colour_manual(values = couleurs[c(1, 4, 6, 9, 11, 15, 20, 25)]) +
labs(title = "Identifiants TLG, noms et genres littéraires (série 1)", y = "name") +
theme(axis.text.y = element_blank())
tlg_authors_df %>%
filter(genre %in% tmp_tlg_authors_genres[9:16]) %>%
ggplot(aes(x = code, y = fct_reorder(name, desc(name)), colour = genre)) +
geom_point() +
xlim(0, 5000) +
scale_colour_manual(values = couleurs[c(1, 4, 6, 9, 11, 15, 20, 25)]) +
labs(title = "Identifiants TLG, noms et genres littéraires (série 2)", y = "name") +
theme(axis.text.y = element_blank())
tlg_authors_df %>%
filter(genre %in% tmp_tlg_authors_genres[17:24]) %>%
ggplot(aes(x = code, y = fct_reorder(name, desc(name)), colour = genre)) +
geom_point() +
xlim(0, 5000) +
scale_colour_manual(values = couleurs[c(1, 4, 6, 9, 11, 15, 20, 25)]) +
labs(title = "Identifiants TLG, noms et genres littéraires (série 3)", y = "name") +
theme(axis.text.y = element_blank())
remove(tmp_tlg_authors_genres)
Rien d’extrêmement clair ne se distingue, en sus de ce que nous avons remarqué auparavant, si ce n’est éventuellement un groupement de poètes lyriques autour de 250 (série 2), et de “Poetae” autour de 2600 (série 3), mais qui ne paraissent pas classés par ordre alphabétique.
Il faudrait regarder les choses plus en détail, et éventuellement modifier ou compléter nos données de départ : rappelons que pour un grand nombre d’auteurs, les genres, récupérés du CLTK, nous sont manquants. De même, les dates retenues pourraient être imprécises, ou les noms pourraient être différents de ceux retenus au moment de la classification – voire, pour être plus précis, les noms pourraient, sans être totalement différents, être indexés à une initiale distincte de la première lettre apparaissant dans le nom dont nous disposons.
Pour la classification PHI, l’essentiel paraît expliqué – il ne resterait qu’à déterminer plus précisément les frontières des périodes considérées, ainsi que les noms utilisés, et éventuellement, la raison des “trous” dans la numérotation.
Il ne nous reste plus qu’à essayer d’expliquer, pour la classification TLG, d’une part l’ordre des séquences, d’autre part certaines séquences particulières, dont pour certaines aucune logique claire n’est apparue jusque-là. Pour l’ordre des séquences entre elles, une telle logique paraît difficile à établir. Pour ce qui est des portions inexpliquées, intéressons-nous en particulier aux identifiants au-delà de 1800, qui ne suivaient vraisemblablement pas des logique alphabétique.
Tentons donc d’observer si les lieux pourraient jouer un rôle dans l’attribution des identifiants numérotés à partir de 1800.
tlg_authors_df %>%
filter(code > 1800 & code < 5000) %>%
drop_na(place) %>%
ggplot(aes(x = code, y = fct_reorder(place, desc(place)), color = place)) +
geom_point() +
scale_color_manual(values = c(couleurs, "#000000", "#000000")) +
labs(title = "Identifiants TLG et lieux (1800-5000)", y = "place")
Rien d’évident ne se dégage de ce premier graphique : un même lieu se répartit souvent tout au long de la numérotation. Essayons de croiser avec les dates pour distinguer des éléments.
Comme la colonne “place” contient 47 lieux différents, cela ne sera pas lisible si nous attribuons une couleur à chacun d’eux. Nous allons déterminer les lieux qui apparaissent le plus fréquemment, pour les codes 1800 et plus.
count_tlg_place <-
tlg_authors_df %>%
filter(code > 1800) %>%
count(place, sort = TRUE)
knit_print(count_tlg_place[1:11,])
Comme nous l’avons fait pour les genres, nous gardons les 8 pays pour lesquels nous observons le plus d’occurrences (ici, au moins 7 occurrences, en choisissant arbitrairement Chypre plutôt que la Libye qui sont ex aequo).
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy",
"Syria", "Israel", "Palestine", "Cyprus"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(1800, 5000) +
ylim(-350, 1600) +
scale_colour_manual(values = couleurs[c(1, 4, 6, 9, 11, 15, 20, 25)]) +
labs(title = "Identifiants TLG, dates et lieux (1800-5000)")
On observe plusieurs lignes verticales représentant des auteurs de Turquie : l’une vers le code 2750, concerne des auteurs d’environ 1200 à environ 1400; l’autre dans les codes 3000 concerne des auteurs de 800 à 1500; il semble également y avoir des lignes concernant des auteurs d’Égypte. Comme il est difficile de lire ce graphique, nous éliminons les lieux pour lesquels nous constatons moins de 30 occurrences, et nous allons diviser les graphiques en tranches de codes, à partir des observations du tableau en deux dimensions “Identifiants TLG et noms (0-5000)”, figurant dans le point 2.1. Les identifiants par les noms. On y voyait des concentrations de points qui ne formaient pas de lignes, pour les codes 1800 à 3000 et 4000 à 4400.
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy", "Syria"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(1800, 3000) +
ylim(-350, 1600) +
scale_colour_manual(values = couleurs[c(1, 7, 13, 19, 25)]) +
labs(title = "Identifiants TLG, dates et 5 lieux (1800-3000)")
Il semblerait que plusieurs lignes se dessinent de manière éparse. De nouveaux zooms sont nécessaires.
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy", "Syria"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(1800, 2100) +
ylim(-350, 750) +
scale_colour_manual(values = couleurs[c(1, 7, 13, 19, 25)]) +
labs(title = "Identifiants TLG, dates et 5 lieux (1800-2100)")
Quelques débuts de lignes apparaissent vers 1950, mais il est difficile d’établir une corrélation entre codes et lieux. La ligne rouge horizontale apparaissant est probablement davantage liée à la date qu’au lieu : lieux et périodes de production d’écrits grecs sont souvent liés.
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy", "Syria"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(2100, 2300) +
ylim(-350, 750) +
scale_colour_manual(values = couleurs[c(1, 7, 13, 19, 25)]) +
labs(title = "Identifiants TLG, dates et 5 lieux (2100-2300)")
Nous constatons des agrégats de points de même couleur, mais il est difficile d’en tirer des conclusions, d’autant plus qu’il manque beaucoup de lieux dont les points de couleurs auraient apporté davantage de bruit.
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy", "Syria"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(2700, 3300) +
ylim(-350, 1600) +
scale_colour_manual(values = couleurs[c(1, 7, 13, 19, 25)]) +
labs(title = "Identifiants TLG, dates et 5 lieux, 2700-3300")
Cette portion a pour caractéristique, de concerner presque exclusivement la Turquie, du moins concernant les zones retenues.
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy",
"Syria", "Israel", "Palestine", "Cyprus"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(2700, 3300) +
ylim(-350, 1600) +
scale_colour_manual(values = couleurs[c(1, 4, 6, 9, 11, 15, 20, 25)]) +
labs(title = "Identifiants TLG, dates et 8 lieux (2700-3300")
Cela semble se confirmer lorsque nous élargissons cette observation aux huit pays les plus importants de la tranche 1800 et plus. Cependant, une recherche élargie à tous les lieux permettrait peut-être d’apporter une conclusion. Malheureusement, elle est impossible avec la pléthore des 52 couleurs, surtout que ggpplot les détermine selon l’ordre alphabétique et non selon la proximité géographique.
tlg_authors_df %>%
filter(place %in% c(
"Turkey", "Greece", "Egypt", "Italy", "Syria"
)) %>%
ggplot(aes(x = code, y = date, colour = place)) +
geom_point() +
xlim(4000, 4400) +
ylim(-350, 1600) +
scale_colour_manual(values = couleurs[c(1, 7, 13, 19, 25)]) +
labs(title = "Identifiants TLG, dates et 5 lieux (4000-4400")
Rien de significatif ne semble émerger de cette tranche.
Rien de très conclusif n’apparaît de cette recherche sur les lieux des auteurs en rapport avec le code TLG au delà de 1800.
Finalement, plusieurs réponses sont nées de ces différents essais. Tout d’abord, il convient de préciser que les réponses qui auraient pu être les plus évidentes pour un œil spécialisé en lettres classiques se sont révélées fausses. Ainsi, les codes attribués aux auteurs ne suivent pas strictement l’ordre alphabétique, comme le montrent les graphiques, ni l’ordre chronologique. Ils ne suivent pas non plus de manière univoque un classement selon les genres littéraires, ni selon l’emplacement géographique des auteurs.
Peu à peu, nos questions se sont affinées et nos réponses ont fait de même. Nous avons découvert que les codes suivaient en réalité plusieurs logiques. Ainsi, plusieurs séquences se sont révélées durant les essais. Par exemple, certaines séquences de codes attribués aux auteurs suivent une logique alphabétique, ce qu’on retrouve à la fois dans les données TLG et dans les données PHI. Cela a posé d’autres problèmes encore : en latin, la question des praenomina s’est révélée d’importance. Après avoir précisé ce que nous voulions, les problèmes qui se posaient à nous et la manière dont nous pourrions y répondre, un découpage par séquence d’identifiants nous est apparu nécessaire pour trouver des réponses plus précises : c’est par exemple ainsi que nous avons compris que les auteurs latins, dans la classification PHI, étaient regroupés par période, et classés par ordre alphabétique au sein de chaque groupe.
Mais les logiques alphabétique et chronologique, même combinées, n’expliquaient parfois pas tout, et il nous a fallu explorer d’autres critères de classification. C’est ainsi que nous avons découvert qu’il pouvait exister des logiques génériques : parfois, les auteurs sont classés selon les genres, et on a par exemple relevé dans les données du TLG que les auteurs “comiques” étaient regroupés ensemble, et ordonnés au sein du groupe par ordre alphabétique ; pour d’autres regroupements comme celui des poetae, la classification interne au groupe était en revanche moins évidente.
Par conséquent, non seulement plusieurs logiques sont à l’œuvre selon plusieurs séquences, mais on peut en outre ajouter que certains facteurs ont pu être combinés. De graphiques en deux dimensions, nous sommes donc passés à des graphiques en trois dimensions. Notons également qu’une combinaison de facteurs pourrait expliquer l’attribution des chiffres pour composer le nombre qui sert de code : les chiffres pourraient correspondre à plusieurs facteurs et seraient combinés pour créer le nombre. Par exemple, les deux premiers chiffres pourraient correspondre à une variable, et les deux suivants à une autre – ces deux chiffres seraient ensuite adjoints pour donner un code à quatre chiffres. Cette hypothèse, encore à explorer, pourrait contribuer à expliquer les lacunes, voire les séquences jusque là incomprises.
Tout d’abord, il faut préciser que, étant donné le temps dont nous disposions, nous n’avons pas pu être plus précis que cela dans nos études de données. Nous avons découpé des séquences mais certaines n’ont pas pu être explorées plus précisément. C’est en particulier le cas pour la séquence de 1800 à 3000 dans les identifiants du TLG ; à son propos, nous ne sommes pas parvenus à offrir de réponses solides, mais seulement quelques hypothèses.
De plus, dans cette analyse dont le volet statistique est limité, nous avons opté pour des graphiques dans l’idée qu’ils représenteraient mieux les premiers résultats à notre portée. Cela comporte bien évidemment des limites : nous n’avons, par exemple, pas pu combiner toutes les catégories de données en même temps, de peur de rendre le graphique illisible. En la matière, la réalisation d’analyses par correspondances multiples (ACP) aurait peut-être pu nous éclairer davantage.
En outre, la nature du TLG et de Perseus sont à prendre en compte. Ce sont des plateformes qui ont connu différentes phases de développement, ce qui joue sans doute un rôle dans les séquences que nous avons pu percevoir : tous les auteurs, toutes les œuvres n’ont pas été numérisées en une seule fois. Le TLG donne un aperçu dès l’entrée dans son site, sur la page d’accueil, de l’évolution des documents numérisés. Les ajouts successifs sont très réguliers. Cela pose donc la question de l’attribution des identifiants à ces nouveaux ajouts.
Ces lacunes sont aussi dues à des manques dans les données qui nous ont servi de point de départ. Nous avons étudié des auteurs antiques, il est donc inévitable que certaines données nous manquent. Parfois, elles n’ont tout simplement pas été intégrées à la base de données TLG ou PHI, qui sont elles aussi forcément lacunaires. De plus, nous aurions pu compléter nos données : pour les genres des auteurs grecs notamment, il aurait été possible de les récupérer directement depuis leurs noms.
De même, les lacunes naissent aussi d’un choix entre les facteurs étudiés. Nous avons choisi d’étudier certains facteurs en particulier : les noms, prénoms, initiales, lieux d’activité, date d’activité. Mais un facteur fondamental d’un point de vue classiciste serait celui des canons. En effet, dans les lettres classiques, certains auteurs considérés comme canons ont été valorisés au détriment d’autres auteurs jugés moins importants. De même, on peut émettre l’hypothèse que les spécialistes qui ont numérisé les premiers ouvrages ont pu fonctionner selon un système de priorité et ont attribué des identifiants spécifiques aux auteurs jugés les plus importants en les numérisant les premiers. Cependant, ce facteur est difficilement formalisable ou quantifiable. Néanmoins, c’est une hypothèse qu’il faut considérer.
Un autre élément que nous avons décidé d’abandonner pour le moment est celui de la préservation des manuscrits. Tous les ouvrages numérisés dans le TLG ou Perseus naissent d’une tradition, directe ou indirecte. Pour une même œuvre, il peut donc y avoir plusieurs manuscrits. Peut-être ces manuscrits similaires mais différents ont-ils donné lieu à des schémas d’attributions d’identifiants spécifiques. Mais c’est un problème qui concerne plus les œuvres que les auteurs en ce qui concerne les auteurs “canons”. Toutefois, si l’on se concentre sur des auteurs moins classiques, dont il ne nous reste qu’un seul manuscrit par exemple, c’est un paramètre qu’il faut prendre en compte.
Pour ce qui est des noms, nous avons vu que le prénom était d’importance : récupérer plus proprement les noms de références des auteurs latins aurait vraisemblablement permis d’affiner l’analyse. En ce qui concerne les auteurs grecs, la même question se pose sans le problème des praenomina : l’initiale devait-elle être celle du prénom ou celle du nom ? Ce problème pourrait expliquer certains des points aberrants que l’on a pu retrouver dans les graphiques concernés.
Pour les lieux, nous sommes conscients que nous avons fait des choix arbitraires en retenant ceux qui comportaient le plus d’occurrences pour les “tranches” d’identifiants étudiées.
Nous aurions pu grouper les lieux par régions géographiques ou au contraire prendre en compte les villes lorsqu’elles étaient précisées et placer les identifiants en dégradés de couleurs sur une carte géographique…
Pour les dates, il aurait également été possible de mener des analyses complémentaires, en particulier pour clarifier précisément le découpage de la classification PHI, avec nos données ou des données supplémentaires, en prenant en compte par exemple la date de naissance et la date de mort ici laissées de côté.
En ce qui concerne les genres, nous avons déjà évoqué le fait que nos sources en la matière étaient lacunaires, et que les genres étaient moins bien classés et documentés que les noms ou les dates par exemple. Il faut en outre ajouter qu’une hiérarchie entre les genres n’a pas été prise en compte dans leur documentation. Par exemple, Poetae est mis sur le même plan que Lyrici par exemple, alors que les poètes lyriques sont eux aussi des poètes.
Enfin, nous avons choisi de nous fonder sur l’hypothèse que les codes sont attribués aux auteurs selon des facteurs qui leur sont propres, et non pas sur leurs œuvres – par exemple, le nombre d’œuvres répertoriées ou leur longueur – et nous ne nous sommes pas penchés sur la logique de numérotation des œuvres pour chaque auteur. Les éléments que nous avons dégagés pourraient constituer le point de départ d’une étude ultérieure qui complèterait la nôtre.
De nombreuses pistes se sont offertes à nous sans que nous puissions les explorer, par manque de temps. Il reste, en particulier dans le TLG, des séquences à expliquer et à explorer. Nous avons choisi de nous concentrer sur ce qui s’offrait le plus immédiatement à notre regard, mais il est possible d’être plus précis encore dans le découpage et l’analyse de ces séquences. De même, nous ne sommes par parvenus à expliquer pourquoi les séquences, dans la classification TLG, adoptent cet ordre spécifique. C’est un élément important dans la logique des attributions des codes, sans que nous ayons pu l’expliquer avec nos données : peut-être que des matériaux ethnographiques quant à l’histoire de la constitution de ces classifications apporteraient à cet endroit des éclairages utiles.
Concernant ce sujet - et d’autres dans cette entreprise -, il serait sans aucun doute utile de constituer une bibliographie. Par exemple, on nous a signalé ce document donnant des précisions intéressantes sur le rôle de l’American Philological Association, dont un comité a sélectionné les textes à inclure : Berkowitz Luci, « Ancilla to the Thesaurus Linguae Graecae : The TLG Canon », dans Jon Solomon (éd.), Accessing Antiquity. The Computerization of Classical Studies, Tucson, University of Arizona Press, 1993, p. 34‑61.
Sélection dans un tableau
tmp <-
tlg_authors_df %>%
# Select columns
select(name, code, date_active) %>%
# Drop missing values
drop_na(date_active)
Récupération des hononymes
#' Get hononyms.
#' @param df Dataframe of authors.
#' @return Dataframe of authors who have hononyms.
get_homonyms_df <- function(df) {
res_df <-
df %>%
count(name, name = "name_n") %>%
filter(name_n > 1) %>%
inner_join(df, by = "name") %>%
relocate(name, name_n, .after = init)
return(res_df)
}
get_homonyms_df(tlg_authors_df)
get_homonyms_df(phi_authors_df)
# We can observe 217 hononyms in `tlg_authors_df`
# and 12 honomnyms in `phi_authors_df` (those last are only n/a).
Explorations subsidiaires des données
tlg_authors_df %>% filter(str_detect(name, "(?i)epistul"))
tlg_authors_df %>% filter(str_detect(name, "(?i)anonym"))
tlg_authors_df %>% filter(str_detect(name, "(?i)paradox"))
write_csv(phi_authors_df, here("projet_final/output/phi_authors_df.csv"))
write_csv(tlg_authors_df, here("projet_final/output/tlg_authors_df.csv"))
Le présent document a été généré le 18 Feb 2021 à 18:04:27 sous Unix avec les ressources suivantes :
Bibliothèques externes
na_p renommée na_rate et arrondie)| Nom original | Version correspondante |
|---|---|
| v0.5 (modifiée) / v0.6 | v0.6 |
| v0.5nad | v0.7 |
| v0.5Nad-7janv | v0.8 |
| v0.5Nad-8janv | v0.9 |
Correction d’une erreur dans la récupération des codes (pour bien récupérer les auteurs dont le code est inférieur à 1000)
Utilisation de la moyenne plutôt que de la valeur de gauche, quand la date donnée est un intervalle (pour la transformer en nombre)
Changements anecdotiques
Changements mineurs
convert_dates(), renommage de is_before en is_bcecode.lubridate